# -*- coding: utf-8 -*-
"""
Module Description:

"""
import time
import threading
import uuid

from common import service_config
from framework.firefly_dc import runtime_logger as log
from framework.redisutils.redis_storage import RedisStorage
from framework.redisutils.lua_script_runner import run_script_from_conn
from framework.core.context import action_context_storage


RELEASE_LUA_SCRIPT = """
    if redis.call('get',KEYS[1]) == ARGV[1] then
        return redis.call('del',KEYS[1])
    else
        return 0
    end
"""


def log_mutex_too_long_spin_time(mutex_obj):
    """
    记录过长的自旋时间日志
    :param mutex_obj: 锁对象
    :return:
    """
    spin_cost_ms = (mutex_obj.hold_ts - mutex_obj.start_try_ts) * 1000
    if spin_cost_ms > service_config.LogSwitchParams.mutex_spin_log_minimum_cost_ms():
        msg = 'MutexTooLongSpinTime: Key({key})    SpinMillisecond({spin_time})  Ctx({ctx})'.format(
            key=mutex_obj.key,
            spin_time=spin_cost_ms,
            ctx=action_context_storage.ctx)
        log.error(msg)


def log_mutex_overtime(mutex_obj):
    """
    记录锁提前失效的日志
    :param mutex_obj: 锁对象
    :return:
    """
    msg = 'MutexOvertime: Key({key});' \
          'SetLockSecond({set_lock_second});' \
          'RealHoldLockSecond({hold_lock_second});' \
          'Ctx({ctx})'.format(key=mutex_obj.key,
                              set_lock_second=mutex_obj.overtime_sec,
                              hold_lock_second=mutex_obj.relese_ts - mutex_obj.hold_ts,
                              ctx=action_context_storage.ctx)
    log.error(msg)


def acquire_lock(db, lock_key, lock_seconds=1, identifier=0):
    """
    尝试获取锁
    :param db: redis连接
    :param lock_key: 锁key
    :param lock_seconds: 申请锁的占用时间
    :param identifier: 锁key的持有者标识
    :return: bool, 是否成功获取锁
    """
    # True -> 加锁成功
    # None -> 加锁失败
    return db.set(lock_key, identifier, ex=lock_seconds, nx=True) is True


def release_lock(db, lock_key, identifier):
    """
    尝试释放锁
    :param db: redis连接
    :param lock_key: 锁key
    :param identifier: 锁key的持有者标识
    :return: int, 释放锁的结果
    """
    # 返回1表示正常情况,由锁持有者释放
    # 返回0表示锁已经提前释放expire(执行时间超过锁的预设时长)
    return run_script_from_conn(db, RELEASE_LUA_SCRIPT, 1, *(lock_key, identifier))


class Mutex(object):
    """
    key互斥锁
    """
    DEFAULT_OVERTIME = service_config.get_lock_default_overtime()

    def __init__(self, key, overtime_sec=None,
                 db=None, need_lock=True, need_log=True, try_lock_timeout=None):
        """
        :param key: 分布式锁key
        :param overtime_sec: 锁超时时间(s)
        :param db: redis连接对象
        :param need_lock: 是否进行加锁
        :param need_log: 是否打印锁日志
        :param try_lock_timeout: 尝试获取锁的超时时间,None表示一直尝试.单位s
        """
        if not key:
            raise ValueError('Mutex Key Invalided!')
        self.overtime_sec = Mutex.DEFAULT_OVERTIME if overtime_sec is None else overtime_sec
        if self.overtime_sec < 0.5:
            raise ValueError('Mutex Overtime(second) Too Short!')
        if not isinstance(self.overtime_sec, int):
            raise ValueError('Mutex Overtime Is Not a Integer!')

        self.key = key
        self.need_lock = need_lock
        self.need_log = need_log

        # 开始争抢锁的时刻
        self.start_try_ts = 0
        # 成功得到锁的时刻
        self.hold_ts = 0
        # 释放锁的时刻
        self.relese_ts = 0

        # 互斥锁的标识，避免解锁时移除了非当前线程持有的锁
        # 因为有可重入特性，因此在进行lock前才设置
        self.identifier = 0

        # 尝试获取锁的超时时间
        self.try_lock_timeout = try_lock_timeout

        # 是否成功获取锁
        self.acquire_lock_success = False if need_lock else True
        # 是否成功释放锁
        self.unlock_success = False if need_lock else True

        if db is None:
            db = self._get_default_redis_conn()
        self.db = db

    def _get_default_redis_conn(self):
        """
        获取默认的redis连接
        :return:
        """
        return RedisStorage.get_conn('lock')

    def lock(self):
        """
        从acquire_lock_success获得加锁成功/失败的结果
        """
        # 设置互斥锁的标识值
        self.identifier = str(uuid.uuid1())
        self.start_try_ts = time.time()
        while 1:
            if acquire_lock(self.db, self.key, lock_seconds=self.lock_timeout, identifier=self.identifier):
                self.acquire_lock_success = True
                break

            if self.try_lock_timeout is not None:
                self.try_lock_timeout -= 0.05
                if self.try_lock_timeout <= 0:
                    self.acquire_lock_success = False
                    break
            time.sleep(0.05)

        if self.acquire_lock_success:
            self.hold_ts = time.time()

        if self.need_log:
            log_mutex_too_long_spin_time(self)

    def unlock(self):
        """
        从unlock_success获得解锁成功/失败的结果
        """
        if not self.acquire_lock_success:
            return

        release_status = release_lock(self.db, self.key, self.identifier)

        self.relese_ts = time.time()

        if not release_status:
            log_mutex_overtime(self)
            self.unlock_success = False
        else:
            self.unlock_success = True

    def __enter__(self):
        if self.need_lock:
            self.lock()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if self.need_lock:
            self.unlock()

    @property
    def lock_timeout(self):
        return self.overtime_sec


class RMutex(Mutex):
    """
    可重入互斥锁
    """
    local = threading.local()

    def __init__(self, key, overtime_sec=None,
                 need_lock=True, try_lock_timeout=None):

        if key in self.local.__dict__:
            need_lock = False

        Mutex.__init__(self, key, overtime_sec=overtime_sec,
                       need_lock=need_lock, try_lock_timeout=try_lock_timeout)

    def decr_lock_not_release_redis(self):
        self.need_lock = False
        self._decr_lock()

    def _incr_lock(self):
        if self.key not in self.local.__dict__:
            self.local.__dict__[self.key] = 1
        else:
            self.local.__dict__[self.key] += 1

    def _decr_lock(self):
        if self.key not in self.local.__dict__:
            raise RuntimeError('Cannot Decrease Lock Counter Without Holding!')

        if self.local.__dict__[self.key] == 1:
            del self.local.__dict__[self.key]
        else:
            self.local.__dict__[self.key] -= 1

    def __enter__(self):
        if self.need_lock:
            self.lock()
            self._incr_lock()
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if self.need_lock:
            self.unlock()
            self._decr_lock()


class CrossAreaMutex(RMutex):
    """
    区服跨服公共锁 存储在区服和跨服都可以访问的redis
    """
    def _get_default_redis_conn(self):
        """
        获取默认的redis连接
        :return:
        """
        return RedisStorage.get_conn('venus_lock')


# 向后兼容
# deprecated
KeyMutex = RMutex

CAMutex = CrossAreaMutex
